#include "asserts.hpp"
#include "foreach.hpp"
#include "formatter.hpp"
#include "player.hpp"
#include "scenario.hpp"
#include "terrain.hpp"
#include "unit_utils.hpp"

namespace game {

graphics::texture get_unit_image(const unit_type& u)
{
	return u.standing_animation().team_animation(1).get_texture();
}

graphics::texture get_unit_image(const unit& u)
{
	return u.type().standing_animation().team_animation(u.get_commander().owner().color_index()).get_texture();
}

bool units_hostile(const unit& a, const unit& b)
{
	return a.get_commander().owner().is_hostile(b.get_commander().owner());
}

int calculate_unit_attack(const unit& u, const unit& target, std::vector<std::string>* explanation)
{
	scenario* scenario = scenario::current();
	ASSERT_LOG(scenario, "Could not find scenario when calculating attack");
	ASSERT_LOG(scenario->map().is_loc_on_map(u.loc()), "Unit not on map when calculating attack");

	const int height_advantage =
	  scenario->map().get_tile(u.loc())->terrain()->base()->height() -
	  scenario->map().get_tile(target.loc())->terrain()->base()->height();

	int result = u.attack();

	if(explanation) {
		explanation->push_back("Base");
		explanation->push_back(formatter() << u.attack());
	}

	if(height_advantage > 0) {
		result += height_advantage;
		if(explanation) {
			explanation->push_back("Height Advantage");
			explanation->push_back(formatter() << "+" << height_advantage);
		}
	}

	const commander_unit& commander = u.get_commander();
	if(&commander != &u) {
		if(hex::distance_between(commander.loc(), u.loc()) <= commander.command_radius()) {
			result += commander.attack_bonus();
			if(explanation) {
				explanation->push_back("Commander");
				explanation->push_back(formatter() << "+" << commander.attack_bonus());
			}
		} else if(explanation) {
			explanation->push_back("Commander");
			explanation->push_back("(out of range)");
		}
	}

	const int range = hex::distance_between(u.loc(), target.loc());
	if(range > 1) {
		const int RangePenalty = 4;
		result -= RangePenalty;
		if(explanation) {
			explanation->push_back("Range");
			explanation->push_back(formatter() << "-" << RangePenalty);
		}
	}

	foreach(const const_unit_modification_ptr& mod, u.modifications()) {
		if(mod->applies(u, target) && mod->attack()) {
			result += mod->attack();
			if(explanation) {
				explanation->push_back(mod->description());
				explanation->push_back(formatter() << "+" << mod->attack());
			}
		}
	}

	return result;
}

int calculate_unit_defense(const unit& u, const unit& target, std::vector<std::string>* explanation)
{
	scenario* scenario = scenario::current();
	ASSERT_LOG(scenario, "Could not find scenario when calculating attack");
	ASSERT_LOG(scenario->map().is_loc_on_map(u.loc()), "Unit not on map when calculating attack");

	int result = u.defense();

	if(explanation) {
		explanation->push_back("Base");
		explanation->push_back(formatter() << result);
	}
	
	const int terrain_bonus = u.terrain_defense_bonus(u.loc());
	if(terrain_bonus) {
		result += terrain_bonus;
		if(explanation) {
			explanation->push_back("Terrain");
			explanation->push_back(formatter() << (terrain_bonus > 0 ? "+" : "") << terrain_bonus);
		}
	}

	const commander_unit& commander = u.get_commander();
	if(&commander != &u) {
		if(hex::distance_between(commander.loc(), u.loc()) <= commander.command_radius()) {
			result += commander.defense_bonus();
			if(explanation) {
				explanation->push_back("Commander");
				explanation->push_back(formatter() << "+" << commander.defense_bonus());
			}
		} else if(explanation) {
			explanation->push_back("Commander");
			explanation->push_back("(out of range)");
		}
	}

	foreach(const const_unit_modification_ptr& mod, u.modifications()) {
		if(mod->applies(u, target) && mod->defense()) {
			result += mod->defense();
			if(explanation) {
				explanation->push_back(mod->description());
				explanation->push_back(formatter() << "+" << mod->attack());
			}
		}
	}

	return result;
}

int unit_color_index(const unit& u)
{
	return u.get_commander().owner().color_index();
}

bool is_commander(const unit& u)
{
	return &u == &u.get_commander();
}

bool is_ezoc(const unit& u, const hex::location& loc)
{
	hex::location adj[6];
	hex::get_adjacent_tiles(loc, adj);

	foreach(const hex::location& a, adj) {
		unit_ptr adjacent_unit = scenario::current()->get_unit(a);
		if(adjacent_unit && units_hostile(u, *adjacent_unit)) {
			return true;
		}
	}

	return false;
}

bool units_moving()
{
	const scenario::UnitMap& map = scenario::current()->unit_map();
	for(scenario::UnitMap::const_iterator i = map.begin(); i != map.end(); ++i) {
		if(i->second->is_moving()) {
			return true;
		}
	}

	return false;
}

TempLocationSetter::TempLocationSetter(unit& u, const hex::location& loc)
  : unit_(u), initial_loc_(u.loc())
{
	unit_.set_loc(loc);
}

TempLocationSetter::~TempLocationSetter()
{
	unit_.set_loc(initial_loc_);
}

}
